/*
 *  linux/kernel/asm.s
 *
 *  (C) 1991  Linus Torvalds
 */

/*
 * asm.s contains the low-level code for most hardware faults.
 * page_exception is handled by the mm, so that isn't here. This
 * file also handles (hopefully) fpu-exceptions due to TS-bit, as
 * the fpu must be properly saved/resored. This hasn't been tested.
 */

.globl divide_error,debug,nmi,int3,overflow,bounds,invalid_op
.globl double_fault,coprocessor_segment_overrun
.globl invalid_TSS,segment_not_present,stack_segment
.globl general_protection,coprocessor_error,irq13,reserved
# 1、当发生中断时，中断处理程序使用的是进程的内核态堆栈。在用户程序（进程）将控制权交给中断处理程序之前，CPU会自动处理如下工作：依次会将如下信息压入内核堆栈：
	# 1.1、 (可选)如果优先级别发生了变化，例如从用户级改变到内核系统级，CPU 会将原代码的堆栈段值（SS）和堆栈指针（ESP）压入中断程序的内核态堆栈中；如果没有发生优先级别变化，则无需压入SS、ESP。
	# 1.2、CPU 会首先将至少 12 字节（EFLAGS、 CS 和 EIP）的信息压入中断处理程序的堆栈(内核态栈)中，此外还会将错误信息（error_code）压入到了目的代码（中断处理程序代码）的堆栈上，而不是被中断代码的堆栈(用户态堆栈)中。
# 2、中断处理程序的处理过程：
	# 2.1、将中断处理函数地址压入内核栈、所有寄存器、出错代码（作为 c 函数参数）、中断返回地址（作为 c 函数参数）依次入栈
	# 2.2、调用 C 处理函数，最后再出栈和中断返回 (iret)。
# 所以中断处理程序返回时，使用iret，会依次弹出EIP、CS、eflags
# 参考图8-2、8-4及相关描述
divide_error:
	pushl $do_divide_error # 将中断处理程序(C函数)地址压入内核堆栈 。  $ 符号，表示获取标号的内存地址
no_error_code:
	xchgl %eax,(%esp) # 将栈顶单元与eax交换：即eax值入栈，中断处理程序(do_divide_error)地址存入eax中
	pushl %ebx
	pushl %ecx
	pushl %edx
	pushl %edi
	pushl %esi
	pushl %ebp
	push %ds
	push %es
	push %fs
	pushl $0		# "error code" (中断处理函数的参数1)
	lea 44(%esp),%edx  # 将操作数表示的偏移地址（用户程序的EIP所在的栈地址）传递到edx。(中断处理函数的参数2)
	pushl %edx
	movl $0x10,%edx  # 设置数据段的选择子。指向内核的数据段
	mov %dx,%ds
	mov %dx,%es
	mov %dx,%fs
	call *%eax  # *号表示调用操作数指定地址处的函数，称为间接调用。此处，调用中断处理函数
	addl $8,%esp  # 将2个中断处理函数的参数出栈(&fuction、error code)
	pop %fs
	pop %es
	pop %ds
	popl %ebp
	popl %esi
	popl %edi
	popl %edx
	popl %ecx
	popl %ebx
	popl %eax
	iret

debug:
	pushl $do_int3		# _do_debug
	jmp no_error_code

nmi:
	pushl $do_nmi
	jmp no_error_code

int3:
	pushl $do_int3
	jmp no_error_code

overflow:
	pushl $do_overflow
	jmp no_error_code

bounds:
	pushl $do_bounds
	jmp no_error_code

invalid_op:
	pushl $do_invalid_op
	jmp no_error_code

coprocessor_segment_overrun:
	pushl $do_coprocessor_segment_overrun
	jmp no_error_code

reserved:
	pushl $do_reserved
	jmp no_error_code

irq13:
	pushl %eax
	xorb %al,%al
	outb %al,$0xF0
	movb $0x20,%al
	outb %al,$0x20
	jmp 1f
1:	jmp 1f
1:	outb %al,$0xA0
	popl %eax
	jmp coprocessor_error

double_fault:
	pushl $do_double_fault
error_code:
	xchgl %eax,4(%esp)		# error code <-> %eax
	xchgl %ebx,(%esp)		# &function <-> %ebx
	pushl %ecx
	pushl %edx
	pushl %edi
	pushl %esi
	pushl %ebp
	push %ds
	push %es
	push %fs
	pushl %eax			# error code
	lea 44(%esp),%eax		# offset（EIP在栈中存储位置：存储在内存单元的地址）
	pushl %eax
	movl $0x10,%eax     # 内核的数据段
	mov %ax,%ds
	mov %ax,%es
	mov %ax,%fs
	call *%ebx
	addl $8,%esp        # 删除堆栈中的 offset、error code
	pop %fs				# 恢复寄存器
	pop %es
	pop %ds
	popl %ebp
	popl %esi
	popl %edi
	popl %edx
	popl %ecx
	popl %ebx
	popl %eax
	iret

invalid_TSS:
	pushl $do_invalid_TSS
	jmp error_code

segment_not_present:
	pushl $do_segment_not_present
	jmp error_code

stack_segment:
	pushl $do_stack_segment
	jmp error_code

general_protection:
	pushl $do_general_protection
	jmp error_code

